/***
 * 风场类
 * date:2022-09-30
 * author：yrg
 * @type {CanvasWindy}
 */
let Cartesian3 = Cesium.Cartesian3
let EllipsoidalOccluder = Cesium.EllipsoidalOccluder
let SceneTransforms = Cesium.SceneTransforms
let Ellipsoid = Cesium.Ellipsoid
let Cartesian2 = Cesium.Cartesian2
let CMath = Cesium.Math


export let CanvasWindy = function (json, params) {
  //风场json数据
  this.windData = json;
  //可配置参数
  viewer = params.viewer;
  this.canvas = params.canvas;
  this.extent = params.extent || [];//风场绘制时的地图范围，范围不应该大于风场数据的范围，顺序：west/east/south/north，有正负区分，如：[110,120,30,36]
  this.canvasContext = params.canvas.getContext("2d");//canvas上下文
  this.canvasWidth = params.canvasWidth || 300;//画板宽度
  this.canvasHeight = params.canvasHeight || 180;//画板高度
  this.speedRate = params.speedRate || 100;//风前进速率，意思是将当前风场横向纵向分成100份，再乘以风速就能得到移动位置，无论地图缩放到哪一级别都是一样的速度，可以用该数值控制线流动的快慢，值越大，越慢，
  this.particlesNumber = params.particlesNumber || 20000;//初始粒子总数，根据实际需要进行调节
  this.maxAge = params.maxAge || 120;//每个粒子的最大生存周期
  this.frameTime = 1000 / (params.frameRate || 10);//每秒刷新次数，因为requestAnimationFrame固定每秒60次的渲染，所以如果不想这么快，就把该数值调小一些
  this.color = params.color || '#ffffff';//线颜色，提供几个示例颜色['#14208e','#3ac32b','#e0761a']
  this.lineWidth = params.lineWidth || 1;//线宽度
  //内置参数
  this.initExtent = [60,0,70,140];//风场初始范围
  this.calc_speedRate = [0, 0];//根据speedRate参数计算经纬度步进长度
  this.windField = null;
  this.particles = [];
  this.animateFrame = null;//requestAnimationFrame事件句柄，用来清除操作
  this.isdistory = false;//是否销毁，进行删除操作
  this._init();
};
CanvasWindy.prototype = {
  constructor: CanvasWindy,
  _init: function () {
    let self = this;
    // 创建风场网格
    this.windField = this.createField();
    this.initExtent = [this.windField.west, this.windField.east, this.windField.south, this.windField.north];
    //如果风场创建时，传入的参数有extent，就根据给定的extent，让随机生成的粒子落在extent范围内
    if (this.extent.length != 0) {
      this.extent = [
        Math.max(this.initExtent[0], this.extent[0]),
        Math.min(this.initExtent[1], this.extent[1]),
        Math.max(this.initExtent[2], this.extent[2]),
        Math.min(this.initExtent[3], this.extent[3])
      ];
    }
    // console.log(this.extent);
    this._calcStep();
    // 创建风场粒子
    for (let i = 0; i < this.particlesNumber; i++) {
      this.particles.push(this.randomParticle(new CanvasParticle()));
    }
    this.canvasContext.fillStyle = "rgba(0, 0, 0, 0.97)";
    this.canvasContext.globalAlpha = 0.6;
    this.animate();

    let then = Date.now();
    (function frame() {
      if (!self.isdistory) {
        self.animateFrame = requestAnimationFrame(frame);
        let now = Date.now();
        let delta = now - then;
        if (delta > self.frameTime) {
          then = now - delta % self.frameTime;
          self.animate();
        }
      } else {
        self.removeLines();
      }
    })();
  },

  setData(data) {
    this.windData = data;
    this.redraw()

  },
  //计算经纬度步进长度
  _calcStep: function () {
    let isextent = (this.extent.length != 0);
    let calcExtent = isextent ? this.extent : this.initExtent;
    let calcSpeed = this.speedRate;
    this.calc_speedRate = [(calcExtent[1] - calcExtent[0]) / calcSpeed, (calcExtent[3] - calcExtent[2]) / calcSpeed];
  },
  //根据现有参数重新生成风场
  redraw: function () {
    window.cancelAnimationFrame(this.animateFrame);
    this.particles = [];
    this._init();
  },
  createField: function () {
    let data = this._parseWindJson();
    return new CanvasWindField(data);
  },
  animate: function () {
    let self = this,
      field = self.windField;
    let nextLng = null,
      nextLat = null,
      uv = null;
    self.particles.forEach(function (particle) {
      if (particle.age == null || particle.age <= 0) {
        self.randomParticle(particle);
      }
      if (particle.age > 0) {
        let x = particle.x,
          y = particle.y,
          tlng = particle.tlng,
          tlat = particle.tlat;
        let gridpos = self._togrid(tlng, tlat);
        let tx = gridpos[0];
        let ty = gridpos[1];
        if (!self.isInExtent(tlng, tlat)) {
          particle.age = 0;
        } else {
          uv = field.getIn(tx, ty);
          nextLng = tlng + self.calc_speedRate[0] * uv[0];
          nextLat = tlat + self.calc_speedRate[1] * uv[1];
          particle.lng = tlng;
          particle.lat = tlat;
          particle.x = tx;
          particle.y = ty;
          particle.tlng = nextLng;
          particle.tlat = nextLat;
          particle.age--;
        }
      }
    });
    if (self.particles.length <= 0) this.removeLines();
    self._drawLines();
  },
  //粒子是否在地图范围内
  isInExtent: function (lng, lat) {
    let calcExtent = this.initExtent;
    if ((lng >= calcExtent[0] && lng <= calcExtent[1]) && (lat >= calcExtent[2] && lat <= calcExtent[3])) return true;
    return false;
  },
  _resize: function (width, height) {
    this.canvasWidth = width;
    this.canvasHeight = height;
  },
  _parseWindJson: function () {
    let uComponent = null,
      vComponent = null,
      header = null;
    this.windData.forEach(function (record) {
      let type = record.header.parameterCategory + "," + record.header.parameterNumber;
      switch (type) {
        case "2,2":
          uComponent = record['data'];
          header = record['header'];
          break;
        case "2,3":
          vComponent = record['data'];
          break;
        default:
          break;
      }
    });
    return {
      header: header,
      uComponent: uComponent,
      vComponent: vComponent
    };
  },
  removeLines: function () {
    window.cancelAnimationFrame(this.animateFrame);
    this.isdistory = true;
    this.canvas.width = 1;
    // document.getElementById('content').removeChild(this.canvas);
  },
  //根据粒子当前所处的位置(棋盘网格位置)，计算经纬度，在根据经纬度返回屏幕坐标
  _tomap: function (lng, lat, particle) {
    let ct3 = Cartesian3.fromDegrees(lng, lat, 0);
    // 判断当前点是否在地球可见端

    let isVisible = new EllipsoidalOccluder(Ellipsoid.WGS84, viewer.camera.position).isPointVisible(ct3);
    let pos = SceneTransforms.wgs84ToWindowCoordinates(viewer.scene, ct3);
    if (!isVisible) {
      particle.age = 0;
    }
    // console.log(pos);
    return pos ? [pos.x, pos.y] : null;
  },
  //根据经纬度，算出棋盘格位置
  _togrid: function (lng, lat) {
    let field = this.windField;
    let x = (lng - this.initExtent[0]) / (this.initExtent[1] - this.initExtent[0]) * (field.cols - 1);
    let y = (this.initExtent[3] - lat) / (this.initExtent[3] - this.initExtent[2]) * (field.rows - 1);
    return [x, y];
  },
  _drawLines: function () {
    let self = this;
    let particles = this.particles;
    this.canvasContext.lineWidth = self.lineWidth;
    //后绘制的图形和前绘制的图形如果发生遮挡的话，只显示后绘制的图形跟前一个绘制的图形重合的前绘制的图形部分，示例：https://www.w3school.com.cn/tiy/t.asp?f=html5_canvas_globalcompop_all
    this.canvasContext.globalCompositeOperation = "destination-in";
    this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
    this.canvasContext.globalCompositeOperation = "lighter";//重叠部分的颜色会被重新计算
    this.canvasContext.globalAlpha = 0.9;
    this.canvasContext.beginPath();
    this.canvasContext.strokeStyle = this.color;
    particles.forEach(function (particle) {
      let movetopos = self._tomap(particle.lng, particle.lat, particle);
      let linetopos = self._tomap(particle.tlng, particle.tlat, particle);
      // console.log(movetopos,linetopos);
      if (movetopos != null && linetopos != null) {
        self.canvasContext.moveTo(movetopos[0], movetopos[1]);
        self.canvasContext.lineTo(linetopos[0], linetopos[1]);
      }
    });
    this.canvasContext.stroke();
  },
  //随机数生成器（小数）
  fRandomByfloat: function (under, over) {
    return under + Math.random() * (over - under);
  },
  //随机数生成器（整数）
  fRandomBy: function (under, over) {
    switch (arguments.length) {
      case 1: return parseInt(Math.random() * under + 1);
      case 2: return parseInt(Math.random() * (over - under + 1) + under);
      default: return 0;
    }
  },
  //根据当前风场extent随机生成粒子
  randomParticle: function (particle) {
    let safe = 30, x = -1, y = -1, lng = null, lat = null;
    let hasextent = this.extent.length != 0;
    let calc_extent = hasextent ? this.extent : this.initExtent;
    try {
      do {
        try {
          if (hasextent) {
            let pos_x = this.fRandomBy(0, this.canvasWidth);
            let pos_y = this.fRandomBy(0, this.canvasHeight);
            let cartesian = viewer.camera.pickEllipsoid(new Cartesian2(pos_x, pos_y), viewer.scene.globe.ellipsoid);
            let cartographic = viewer.scene.globe.ellipsoid.cartesianToCartographic(cartesian);
            if (cartographic) {
              //将弧度转为度的十进制度表示
              lng = CMath.toDegrees(cartographic.longitude);
              lat = CMath.toDegrees(cartographic.latitude);
            }
          } else {
            lng = this.fRandomByfloat(calc_extent[0], calc_extent[1]);
            lat = this.fRandomByfloat(calc_extent[2], calc_extent[3]);
          }
        } catch (e) {

        }
        if (lng) {
          let gridpos = this._togrid(lng, lat);
          x = gridpos[0];
          y = gridpos[1];
        }
      } while (this.windField.getIn(x, y)[2] <= 0 && safe++ < 30);
    } catch (e) {
    }
    try {
      let field = this.windField;
      let uv = field.getIn(x, y);
      particle.speed = uv[2];
      particle.lng = lng;
      particle.lat = lat;
      particle.x = x;
      particle.y = y;
      let nextLng = lng + this.calc_speedRate[0] * uv[0];
      let nextLat = lat + this.calc_speedRate[1] * uv[1];
      particle.tlng = nextLng;
      particle.tlat = nextLat;
      particle.age = Math.round(Math.random() * this.maxAge);//每一次生成都不一样

    }
    catch (e) {

    }
    return particle;
  }
};


/****
 *棋盘类
 *根据风场数据生产风场棋盘网格
 ****/
let CanvasWindField = function (obj) {
  this.west = null;
  this.east = null;
  this.south = null;
  this.north = null;
  this.rows = null;
  this.cols = null;
  this.dx = null;
  this.dy = null;
  this.unit = null;
  this.date = null;

  this.grid = null;
  this._init(obj);
};
CanvasWindField.prototype = {
  constructor: CanvasWindField,
  _init: function (obj) {
    let header = obj.header,
      uComponent = obj['uComponent'],
      vComponent = obj['vComponent'];
    this.west = +header['lo1'];
    this.east = +header['lo2'];
    this.south = +header['la2'];
    this.north = +header['la1'];
    this.rows = +header['ny'];
    this.cols = +header['nx'];
    this.dx = +header['dx'];
    this.dy = +header['dy'];
    this.unit = header['parameterUnit'];
    this.date = header['refTime'];

    this.grid = [];
    let k = 0,
      rows = null,
      uv = null;
    for (let j = 0; j < this.rows; j++) {
      rows = [];
      for (let i = 0; i < this.cols; i++, k++) {
        uv = this._calcUV(uComponent[k], vComponent[k]);
        rows.push(uv);
      }
      this.grid.push(rows);
    }
  },
  _calcUV: function (u, v) {
    return [+u, +v, Math.sqrt(u * u + v * v)];
  },
  //二分差值算法计算给定节点的速度
  _bilinearInterpolation: function (x, y, g00, g10, g01, g11) {
    let rx = (1 - x);
    let ry = (1 - y);
    let a = rx * ry, b = x * ry, c = rx * y, d = x * y;
    let u = g00[0] * a + g10[0] * b + g01[0] * c + g11[0] * d;
    let v = g00[1] * a + g10[1] * b + g01[1] * c + g11[1] * d;
    return this._calcUV(u, v);
  },
  getIn: function (x, y) {
    // if (x < 0 || x >= 359 || y >= 180) {
    //   return [0, 0, 0];
    // }
    let x0 = Math.floor(x),
      y0 = Math.floor(y),
      x1, y1;
    if (x0 === x && y0 === y) return this.grid[y][x];

    x1 = x0 + 1;
    y1 = y0 + 1;

    let g00 = this.getIn(x0, y0),
      g10 = this.getIn(x1, y0),
      g01 = this.getIn(x0, y1),
      g11 = this.getIn(x1, y1);
    let result = null;
    try {
      result = this._bilinearInterpolation(x - x0, y - y0, g00, g10, g01, g11);
    } catch (e) {
      // console.log(x,y);
    }
    return result;
  },
  isInBound: function (x, y) {
    if ((x >= 0 && x < this.cols - 1) && (y >= 0 && y < this.rows - 1)) return true;
    return false;
  }
};


/****
 *粒子对象
 ****/
let CanvasParticle = function () {
  this.lng = null;//粒子初始经度
  this.lat = null;//粒子初始纬度
  this.x = null;//粒子初始x位置(相对于棋盘网格，比如x方向有360个格，x取值就是0-360，这个是初始化时随机生成的)
  this.y = null;//粒子初始y位置(同上)
  this.tlng = null;//粒子下一步将要移动的经度，这个需要计算得来
  this.tlat = null;//粒子下一步将要移动的y纬度，这个需要计算得来
  this.age = null;//粒子生命周期计时器，每次-1
  this.speed = null;//粒子移动速度，可以根据速度渲染不同颜色
};